module test_core(
    input clk_50m,
    input rst_core_n,
    input cpuif_core_test_start,
    input cpuif_core_test_end,

    output core_cpuif_d_err,
    output core_cpuif_a_err,
    output core_cpuif_s_end,
    output core_cpuif_s_busy,

    input [23:0]cpuif_core_test_00,
    input [23:0]cpuif_core_test_01,
    input [23:0]cpuif_core_test_02,
    input [23:0]cpuif_core_test_03,
    input [23:0]cpuif_core_test_04,
    input [23:0]cpuif_core_test_05,
    input [23:0]cpuif_core_test_06,
    input [23:0]cpuif_core_test_07,
    input [23:0]cpuif_core_test_08,
    input [23:0]cpuif_core_test_09,
    input [23:0]cpuif_core_test_10,
    input [23:0]cpuif_core_test_11,
    input [23:0]cpuif_core_test_12,
    input [23:0]cpuif_core_test_13,
    input [23:0]cpuif_core_test_14,
    input [23:0]cpuif_core_test_15,
    
    output core_spt_cs_n,
    output core_spt_we_n,
    output [9: 0]core_spt_addr,
    input  [23:0]spt_core_rdata,
    output [23:0]core_spt_wdata,
    output core_spt_wdata_oe_n
);

// -------------------------------------------------------------------------------
// parameter
// -------------------------------------------------------------------------------
parameter ADDR_MAX = 10'h3FF;

localparam S_IDLE      = 2'd0;
localparam S_DATA_SCAN = 2'd1;
localparam S_ADDR_SCAN = 2'd2;

// -------------------------------------------------------------------------------
// signal declare
// -------------------------------------------------------------------------------
wire data_scan_over;
wire addr_scan_over;

// -------------------------------------------------------------------------------
// FSM
// -------------------------------------------------------------------------------
reg [1:0]state, nx_state;
always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n) state <= S_IDLE;
    else            state <= nx_state;
end

always @* begin
    case(state)
        S_IDLE: begin
            if(cpuif_core_test_start) nx_state = S_DATA_SCAN;
            else                      nx_state = S_IDLE;
        end
        S_DATA_SCAN: begin
            if(cpuif_core_test_end) nx_state = S_IDLE;
            else if(data_scan_over) nx_state = S_ADDR_SCAN;
            else                    nx_state = S_DATA_SCAN;
        end
        S_ADDR_SCAN: begin
            if(cpuif_core_test_end) nx_state = S_IDLE;
            else if(addr_scan_over) nx_state = S_IDLE;
            else                    nx_state = S_ADDR_SCAN;
        end
        default: nx_state = S_IDLE;
    endcase
end

// -------------------------------------------------------------------------------
// data scan
// -------------------------------------------------------------------------------
reg [3:0]core_test_layer;//0~15
reg data_scan_stage;//0: write to sram, 1: read from sram and check
reg data_scan_wirte_repeat;

wire      ds_sram_cs_n;
wire      ds_sram_we_n;
wire      ds_sram_wdata_oe_n;
reg [9 :0]ds_sram_addr;
wire[23:0]ds_sram_wdata;

wire data_scan_setup = (state != S_DATA_SCAN && nx_state == S_DATA_SCAN) || cpuif_core_test_start;
wire data_scan_power = (state == S_DATA_SCAN);

//status: addr 'h0 -> 'h3ff => stage change => layer add 1
always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)          
        ds_sram_addr <= 10'd0;
    else if(data_scan_setup) 
        ds_sram_addr <= 10'd0;
    else if(data_scan_power)begin
        if(ds_sram_addr == ADDR_MAX && data_scan_wirte_repeat == 1'b0 && data_scan_stage == 1'b0)
            ds_sram_addr <= ADDR_MAX;
        else if(ds_sram_addr == ADDR_MAX)
            ds_sram_addr <= 10'd0;
        else                         
            ds_sram_addr <= ds_sram_addr + 1'b1;
    end
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n) 
        data_scan_wirte_repeat <= 1'b0;
    else if(data_scan_setup)
        data_scan_wirte_repeat <= 1'b0;
    else if(data_scan_stage == 1'b0 && data_scan_wirte_repeat == 1'b0 && ds_sram_addr == ADDR_MAX)
        data_scan_wirte_repeat <= 1'b1;
    else if(ds_sram_addr == 10'd0)
        data_scan_wirte_repeat <= 1'b0;
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)                   
        data_scan_stage <= 1'b0;
    else if(data_scan_setup)          
        data_scan_stage <= 1'b0;
    else if(ds_sram_addr == ADDR_MAX && data_scan_wirte_repeat == 1'b1 && data_scan_stage == 1'b0) 
        data_scan_stage <= 1'b1;
    else if(ds_sram_addr == ADDR_MAX && data_scan_stage == 1'b1)
        data_scan_stage <= 1'b0;
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)          
        core_test_layer <= 4'd0;
    else if(data_scan_setup) 
        core_test_layer <= 4'd0;
    else if(ds_sram_addr == ADDR_MAX && data_scan_stage == 1'b1) 
        core_test_layer <= core_test_layer + 1'b1;
end

assign data_scan_over = (core_test_layer == 4'd15 && data_scan_stage == 1'b1 && ds_sram_addr == ADDR_MAX);

//sram output
wire [23:0] ds_sram_wdata_sel;

assign ds_sram_cs_n       = ~data_scan_power;
assign ds_sram_we_n       = data_scan_stage;
assign ds_sram_wdata_oe_n = (data_scan_stage || data_scan_wirte_repeat);
assign ds_sram_wdata_sel  = ((core_test_layer == 4'd0) ? cpuif_core_test_00 : 24'b0) |
                            ((core_test_layer == 4'd1) ? cpuif_core_test_01 : 24'b0) |
                            ((core_test_layer == 4'd2) ? cpuif_core_test_02 : 24'b0) |
                            ((core_test_layer == 4'd3) ? cpuif_core_test_03 : 24'b0) |
                            ((core_test_layer == 4'd4) ? cpuif_core_test_04 : 24'b0) |
                            ((core_test_layer == 4'd5) ? cpuif_core_test_05 : 24'b0) |
                            ((core_test_layer == 4'd6) ? cpuif_core_test_06 : 24'b0) |
                            ((core_test_layer == 4'd7) ? cpuif_core_test_07 : 24'b0) |
                            ((core_test_layer == 4'd8) ? cpuif_core_test_08 : 24'b0) |
                            ((core_test_layer == 4'd9) ? cpuif_core_test_09 : 24'b0) |
                            ((core_test_layer == 4'd10) ? cpuif_core_test_10 : 24'b0) |
                            ((core_test_layer == 4'd11) ? cpuif_core_test_11 : 24'b0) |
                            ((core_test_layer == 4'd12) ? cpuif_core_test_12 : 24'b0) |
                            ((core_test_layer == 4'd13) ? cpuif_core_test_13 : 24'b0) |
                            ((core_test_layer == 4'd14) ? cpuif_core_test_14 : 24'b0) |
                            ((core_test_layer == 4'd15) ? cpuif_core_test_15 : 24'b0) ;
assign ds_sram_wdata      = data_scan_wirte_repeat ? ~ds_sram_wdata_sel : ds_sram_wdata_sel;

// -------------------------------------------------------------------------------
// addr scan
// -------------------------------------------------------------------------------
reg addr_scan_stage;//0: write to sram, 1: read from sram and check

wire      as_sram_cs_n;
wire      as_sram_we_n;
wire      as_sram_wdata_oe_n;
reg [9 :0]as_sram_addr;
wire[23:0]as_sram_wdata;

wire addr_scan_setup = (state != S_ADDR_SCAN && nx_state == S_ADDR_SCAN);
wire addr_scan_power = (state == S_ADDR_SCAN);

//status: addr 'h0 -> 'h3ff => stage change => layer add 1
always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)          
        as_sram_addr <= 10'd0;
    else if(addr_scan_setup) 
        as_sram_addr <= 10'd0;
    else if(addr_scan_power)begin
        if(as_sram_addr == ADDR_MAX)
            as_sram_addr <= 10'd0;
        else                         
            as_sram_addr <= as_sram_addr + 1'b1;
    end
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)                   
        addr_scan_stage <= 1'b0;
    else if(addr_scan_setup)          
        addr_scan_stage <= 1'b0;
    else if(as_sram_addr == ADDR_MAX) 
        addr_scan_stage <= ~addr_scan_stage;
end

assign addr_scan_over = (as_sram_addr == ADDR_MAX && addr_scan_stage == 1'b1);

assign as_sram_cs_n       = ~addr_scan_power;
assign as_sram_we_n       = addr_scan_stage;
assign as_sram_wdata_oe_n = as_sram_we_n;
assign as_sram_wdata      = {14'd0, as_sram_addr};

// -------------------------------------------------------------------------------
// sram out
// -------------------------------------------------------------------------------

assign core_spt_cs_n        = data_scan_power ? ds_sram_cs_n : as_sram_cs_n;
assign core_spt_we_n        = data_scan_power ? ds_sram_we_n : as_sram_we_n;
assign core_spt_addr        = data_scan_power ? ds_sram_addr : as_sram_addr;
assign core_spt_wdata       = data_scan_power ? ds_sram_wdata : as_sram_wdata;
assign core_spt_wdata_oe_n  = data_scan_power ? ds_sram_wdata_oe_n : as_sram_wdata_oe_n;

// -------------------------------------------------------------------------------
// data check
// -------------------------------------------------------------------------------
reg check_power;
reg [23:0]check_data;
reg data_scan_power_ff;
reg addr_scan_power_ff;

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n) 
        check_power <= 1'b0;
    else if(!core_spt_cs_n && core_spt_we_n)
        check_power <= 1'b1;
    else
        check_power <= 1'b0;
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n) 
        check_data <= 24'b0;
    else if(data_scan_power)begin
        if(!ds_sram_cs_n && !ds_sram_we_n && !ds_sram_wdata_oe_n && ds_sram_addr == ADDR_MAX)
            check_data <= ds_sram_wdata;
    end
    else if(addr_scan_power)begin
        if(!as_sram_cs_n && as_sram_we_n)
            check_data <= {14'b0, as_sram_addr};
    end
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)
        data_scan_power_ff <= 1'b0;
    else
        data_scan_power_ff <= data_scan_power;
end

always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)
        addr_scan_power_ff <= 1'b0;
    else
        addr_scan_power_ff <= addr_scan_power;
end

reg core_cpuif_s_end_q;
always @(posedge clk_50m or negedge rst_core_n)begin
    if(!rst_core_n)
        core_cpuif_s_end_q <= 1'b0;
    else if(state == S_ADDR_SCAN && nx_state == S_IDLE)
        core_cpuif_s_end_q <= 1'b1;
    else
        core_cpuif_s_end_q <= 1'b0;
end

assign core_cpuif_s_busy = (state != S_IDLE);
assign core_cpuif_s_end  = core_cpuif_s_end_q;
assign core_cpuif_d_err  = (data_scan_power_ff && check_power && (check_data != spt_core_rdata));
assign core_cpuif_a_err  = (addr_scan_power_ff && check_power && (check_data != spt_core_rdata));

endmodule
